﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/**
 * @file
 * 基于《Game Programming Gems 1》第3.1章的有限状态机系统
 * 作者：Roberto Cezar Bianchini，2010年7月
 * 
 * 使用说明：
 * 1. 在相应的枚举中放置有限状态系统的转换和状态标签。
 * 2. 编写继承自FSMState的新类，并用（转换-状态）对填充每个类。
 *    这些对表示如果FSMSystem在状态S1时触发转换T，则应该处于状态S2。
 *    请记住，这是一个确定性有限状态机（FSM）。一个转换不能导致两个不同的状态。
 *    方法Reason用于确定应该触发哪个转换。
 *    你可以在其他地方编写触发转换的代码，如果认为更适合你的项目，可以留空这个方法。
 *    方法Act包含NPC处于该状态时应该执行的动作的代码。
 *    你可以在其他地方编写动作代码，如果认为更适合你的项目，可以留空这个方法。
 * 3. 创建一个FSMSystem类的实例，并向其中添加状态。
 * 4. 在你的Update或FixedUpdate方法中调用Reason和Act（或你用于触发转换和使NPC在游戏中表现行为的任何其他方法）。
 *    Unity引擎的异步转换，如OnTriggerEnter、SendMessage，也可以使用，
 *    只需在事件发生时调用FSMSystem实例的PerformTransition方法，并传入正确的转换。
 *
 * 软件按“原样”提供，没有任何明示或暗示的担保，
 * 包括但不限于适销性、特定用途的适用性和非侵权性的担保。
 * 在任何情况下，作者或版权持有人均不对因软件或软件的使用或其他交易而产生的任何索赔、
 * 损害或其他责任（无论是合同诉讼、侵权诉讼还是其他诉讼）负责。
 */

/// <summary>
/// 在此枚举中放置转换的标签。
/// 不要更改第一个标签NullTransition，因为FSMSystem类使用它。
/// </summary>
public enum Transition
{
    /// <summary>
    /// 使用此转换来表示系统中不存在的转换。
    /// </summary>
    NullTransition = 0,
    /// <summary>
    /// 开始按钮点击转换。
    /// </summary>
    StartButtonClick,
    /// <summary>
    /// 暂停按钮点击转换。
    /// </summary>
    PauseButtonClick
}

/// <summary>
/// 在此枚举中放置状态的标签。
/// 不要更改第一个标签NullStateID，因为FSMSystem类使用它。
/// </summary>
public enum StateID
{
    /// <summary>
    /// 使用此ID来表示系统中不存在的状态。
    /// </summary>
    NullStateID = 0,
    /// <summary>
    /// 菜单状态。
    /// </summary>
    Menu,
    /// <summary>
    /// 播放状态。
    /// </summary>
    Play,
    /// <summary>
    /// 暂停状态。
    /// </summary>
    Pause,
    /// <summary>
    /// 游戏结束状态。
    /// </summary>
    GameOver
}

/// <summary>
/// 此类表示有限状态系统中的状态。
/// 每个状态都有一个包含（转换-状态）对的字典，显示
/// 如果在当前状态触发转换，FSM应该处于哪个状态。
/// 方法Reason用于确定应该触发哪个转换。
/// 方法Act包含NPC处于该状态时应该执行的动作的代码。
/// </summary>
public abstract class FSMState : MonoBehaviour
{
    /// <summary>
    /// 控制器对象。
    /// </summary>
    protected Ctrl ctrl;
    /// <summary>
    /// 控制器对象的公共属性设置器。
    /// </summary>
    public Ctrl CTRL { set { ctrl = value; } }
    /// <summary>
    /// 有限状态机系统对象。
    /// </summary>
    protected FSMSystem fsm;
    /// <summary>
    /// 有限状态机系统对象的公共属性设置器。
    /// </summary>
    public FSMSystem FSM { set { fsm = value; } }
    /// <summary>
    /// 包含（转换-状态）对的字典。
    /// </summary>
    protected Dictionary<Transition, StateID> map = new Dictionary<Transition, StateID>();
    /// <summary>
    /// 当前状态ID。
    /// </summary>
    protected StateID stateID;
    /// <summary>
    /// 当前状态ID的公共属性获取器。
    /// </summary>
    public StateID ID { get { return stateID; } }

    /// <summary>
    /// 向此状态添加转换。
    /// </summary>
    /// <param name="trans">转换。</param>
    /// <param name="id">状态ID。</param>
    public void AddTransition(Transition trans, StateID id)
    {
        // Check if anyone of the args is invalid
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed for a real transition");
            return;
        }

        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSMState ERROR: NullStateID is not allowed for a real ID");
            return;
        }

        // Since this is a Deterministic FSM,
        //   check if the current transition was already inside the map
        if (map.ContainsKey(trans))
        {
            Debug.LogError("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() +
                           "Impossible to assign to another state");
            return;
        }

        map.Add(trans, id);
    }

    /// <summary>
    /// 从此状态的映射中删除转换-状态对。
    /// </summary>
    /// <param name="trans">转换。</param>
    public void DeleteTransition(Transition trans)
    {
        // Check for NullTransition
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSMState ERROR: NullTransition is not allowed");
            return;
        }

        // Check if the pair is inside the map before deleting
        if (map.ContainsKey(trans))
        {
            map.Remove(trans);
            return;
        }
        Debug.LogError("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() +
                       " was not on the state's transition list");
    }

    /// <summary>
    /// 返回如果此状态接收到转换，FSM应该处于的新状态。
    /// </summary>
    /// <param name="trans">转换。</param>
    /// <returns>新状态ID。</returns>
    public StateID GetOutputState(Transition trans)
    {
        // Check if the map has this transition
        if (map.ContainsKey(trans))
        {
            return map[trans];
        }
        return StateID.NullStateID;
    }

    /// <summary>
    /// 在进入此状态之前设置状态条件。
    /// 由FSMSystem类在将其分配为当前状态之前自动调用。
    /// </summary>
    public virtual void DoBeforeEntering() { }

    /// <summary>
    /// 在FSMSystem更改为另一个状态之前执行任何必要的操作，如重置变量。
    /// 由FSMSystem在更改到新状态之前自动调用。
    /// </summary>
    public virtual void DoBeforeLeaving() { }

    /// <summary>
    /// 决定状态是否应该转换到其列表中的另一个状态。
    /// NPC是受此类控制的对象的引用。
    /// </summary>
    public virtual void Reason() { }

    /// <summary>
    /// 控制NPC在游戏世界中的行为。
    /// NPC所做的任何动作、移动或通信都应放在此处。
    /// NPC是受此类控制的对象的引用。
    /// </summary>
    public virtual void Act() { }
} // class FSMState

/// <summary>
/// @brief 表示有限状态机（Finite State Machine）的类。
/// 包含NPC可能拥有的状态列表，以及添加、删除状态，和更改当前状态的方法。
/// </summary>

public class FSMSystem
{
    /**
     * @brief 存储NPC可能拥有的状态列表。
     */
    private List<FSMState> states;

    /**
     * @brief 当前状态的唯一标识符。
     * @note 只能通过执行转换来改变FSM的状态，不能直接更改CurrentStateID。
     */
    private StateID currentStateID;
    /**
     * @brief 获取当前状态的唯一标识符。
     */
    public StateID CurrentStateID { get { return currentStateID; } }

    /**
     * @brief 当前状态对象。
     */
    private FSMState currentState;
    /**
     * @brief 获取当前状态对象。
     */
    public FSMState CurrentState { get { return currentState; } }

    /**
     * @brief FSMSystem类的构造函数。
     */
    public FSMSystem()
    {
        states = new List<FSMState>();
    }

    /**
     * @brief 设置当前状态。
     * 
     * @param s 要设置为当前状态的状态对象。
     */
    public void SetCurrentState(FSMState s)
    {
        currentState = s;
        currentStateID = s.ID;
        s.DoBeforeEntering(); // 在进入状态前执行的操作
    }

    /**
     * @brief 向FSM中添加新状态。
     * 
     * @param s 要添加的状态对象。
     * @param ctrl 控制对象，与状态相关联。
     * @note 如果状态已经存在于列表中，则打印错误信息。第一个添加的状态也被视为初始状态。
     */
    public void AddState(FSMState s, Ctrl ctrl)
    {
        // 检查空引用
        if (s == null)
        {
            Debug.LogError("FSM ERROR: 不允许空引用");
        }
        s.FSM = this;
        s.CTRL = ctrl;
        // 如果列表为空，则添加第一个状态，并将其设为初始状态
        if (states.Count == 0)
        {
            states.Add(s);
            return;
        }

        // 如果状态已存在，则打印错误信息
        foreach (FSMState state in states)
        {
            if (state.ID == s.ID)
            {
                Debug.LogError("FSM ERROR: 无法添加状态 " + s.ID.ToString() + "，因为该状态已存在");
                return;
            }
        }
        states.Add(s);
    }

    /**
     * @brief 从FSM列表中删除指定状态。
     * 
     * @param id 要删除的状态的唯一标识符。
     * @note 如果状态不存在于列表中，则打印错误信息。
     */
    public void DeleteState(StateID id)
    {
        // 检查空状态ID
        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSM ERROR: NullStateID不允许作为真实状态");
            return;
        }

        // 在列表中搜索并删除状态
        foreach (FSMState state in states)
        {
            if (state.ID == id)
            {
                states.Remove(state);
                return;
            }
        }
        Debug.LogError("FSM ERROR: 无法删除状态 " + id.ToString() + "。它不在状态列表中");
    }

    /**
     * @brief 尝试根据当前状态和传递的转换来改变FSM的状态。
     * 
     * @param trans 要执行的转换。
     * @note 如果当前状态没有为目标转换指定的目标状态，则打印错误信息。
     */
    public void PerformTransition(Transition trans)
    {
        // 检查空转换
        if (trans == Transition.NullTransition)
        {
            Debug.LogError("FSM ERROR: NullTransition不允许作为真实转换");
            return;
        }

        // 检查当前状态是否有指定的转换
        StateID id = currentState.GetOutputState(trans);
        Debug.Log("StateID:"+id);
        if (id == StateID.NullStateID)
        {
            Debug.LogError("FSM ERROR: 状态 " + currentStateID.ToString() + " 没有为转换 " + trans.ToString() + " 指定目标状态");
            return;
        }

        // 更新currentStateID和currentState
        currentStateID = id;
        foreach (FSMState state in states)
        {
            if (state.ID == currentStateID)
            {
                // 在设置新状态之前执行离开当前状态的后处理
                currentState.DoBeforeLeaving();

                currentState = state;

                // 在状态可以推理或行动之前，将其重置为所需状态
                currentState.DoBeforeEntering();
                break;
            }
        }
    } // PerformTransition()

} // class FSMSystem